/**
******************************************************************************
* @file    usbh_stdreq.c
* @author  MCD Application Team
* @version V2.2.0
* @date    09-November-2015
* @brief   This file implements the standard requests for device enumeration
******************************************************************************
* @attention
*
* <h2><center>&copy; COPYRIGHT 2015 STMicroelectronics</center></h2>
*
* Licensed under MCD-ST Liberty SW License Agreement V2, (the "License");
* You may not use this file except in compliance with the License.
* You may obtain a copy of the License at:
*
*        http://www.st.com/software_license_agreement_liberty_v2
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
******************************************************************************
*/

#include "usbh_ioreq.h"
#include "usbh_stdreq.h"

static void USBH_ParseDevDesc (USBH_DevDesc_TypeDef* , uint8_t *buf, uint16_t length);

USBH_Status USBH_ParseCfgDesc (USBH_CfgDesc_TypeDef* cfg_desc,
                               USBH_InterfaceDesc_TypeDef* itf_desc,
                               USBH_EpDesc_TypeDef  ep_desc[][USBH_MAX_NUM_ENDPOINTS],
                               uint8_t *buf,
                               uint16_t length);


static void USBH_ParseInterfaceDesc (USBH_InterfaceDesc_TypeDef  *if_descriptor, uint8_t *buf);
static void USBH_ParseEPDesc (USBH_EpDesc_TypeDef  *ep_descriptor, uint8_t *buf);

static void USBH_ParseStringDesc (uint8_t* psrc, uint8_t* pdest, uint16_t length);

/**
 * @brief  USBH_Get_DevDesc
 *         Issue Get Device Descriptor command to the device. Once the response
 *         received, it parses the device descriptor and updates the status.
 * @param  pdev: Selected device
 * @param  dev_desc: Device Descriptor buffer address
 * @param  Cfg_Rx_Buffer: Receive Buffer address
 * @param  length: Length of the descriptor
 * @retval Status
 */
USBH_Status USBH_Get_DevDesc(USB_OTG_CORE_HANDLE *pdev,
                             USBH_HOST *phost,
                             uint8_t length)
{

    USBH_Status status;

    if((status = USBH_GetDescriptor(pdev,
                                    phost,
                                    USB_REQ_RECIPIENT_DEVICE | USB_REQ_TYPE_STANDARD,
                                    USB_DESC_DEVICE,
                                    Cfg_Rx_Buffer,
                                    length)) == USBH_OK)
    {
        /* Commands successfully sent and Response Received */
        USBH_ParseDevDesc(&phost->device_prop.Dev_Desc, Cfg_Rx_Buffer, length);
    }
    return status;
}

/**
 * @brief  USBH_Get_CfgDesc
 *         Issues Configuration Descriptor to the device. Once the response
 *         received, it parses the configuration descriptor and updates the
 *         status.
 * @param  pdev: Selected device
 * @param  cfg_desc: Configuration Descriptor address
 * @param  itf_desc: Interface Descriptor address
 * @param  ep_desc: Endpoint Descriptor address
 * @param  length: Length of the descriptor
 * @retval Status
 */
USBH_Status USBH_Get_CfgDesc(USB_OTG_CORE_HANDLE *pdev,
                             USBH_HOST           *phost,
                             uint16_t length)

{
    USBH_Status status, result;

    if((status = USBH_GetDescriptor(pdev,
                                    phost,
                                    USB_REQ_RECIPIENT_DEVICE | USB_REQ_TYPE_STANDARD,
                                    USB_DESC_CONFIGURATION,
                                    Cfg_Rx_Buffer,
                                    length)) == USBH_OK)
    {
        /* Commands successfully sent and Response Received  */
        result = USBH_ParseCfgDesc (&phost->device_prop.Cfg_Desc,
                                    phost->device_prop.Itf_Desc,
                                    phost->device_prop.Ep_Desc,
                                    Cfg_Rx_Buffer,
                                    length);
        if (result != USBH_OK)  status= result;


    }
    return status;
}


/**
 * @brief  USBH_Get_StringDesc
 *         Issues string Descriptor command to the device. Once the response
 *         received, it parses the string descriptor and updates the status.
 * @param  pdev: Selected device
 * @param  string_index: String index for the descriptor
 * @param  buff: Buffer address for the descriptor
 * @param  length: Length of the descriptor
 * @retval Status
 */
USBH_Status USBH_Get_StringDesc(USB_OTG_CORE_HANDLE *pdev,
                                USBH_HOST *phost,
                                uint8_t string_index,
                                uint8_t *buff,
                                uint16_t length)
{
    USBH_Status status;

    if((status = USBH_GetDescriptor(pdev,
                                    phost,
                                    USB_REQ_RECIPIENT_DEVICE | USB_REQ_TYPE_STANDARD,
                                    USB_DESC_STRING | string_index,
                                    Cfg_Rx_Buffer,
                                    512)) == USBH_OK)
    {
        /* Commands successfully sent and Response Received  */
        USBH_ParseStringDesc(Cfg_Rx_Buffer, buff, length);
    }
    return status;
}

/**
 * @brief  USBH_GetDescriptor
 *         Issues Descriptor command to the device. Once the response received,
 *         it parses the descriptor and updates the status.
 * @param  pdev: Selected device
 * @param  req_type: Descriptor type
 * @param  value_idx: wValue for the GetDescriptr request
 * @param  buff: Buffer to store the descriptor
 * @param  length: Length of the descriptor
 * @retval Status
 */
USBH_Status USBH_GetDescriptor(USB_OTG_CORE_HANDLE *pdev,
                               USBH_HOST           *phost,
                               uint8_t  req_type,
                               uint16_t value_idx,
                               uint8_t* buff,
                               uint16_t length )
{
    phost->Control.setup.b.bmRequestType = USB_D2H | req_type;
    phost->Control.setup.b.bRequest = USB_REQ_GET_DESCRIPTOR;
    phost->Control.setup.b.wValue.w = value_idx;

    if ((value_idx & 0xff00) == USB_DESC_STRING)
    {
        phost->Control.setup.b.wIndex.w = 0x0409;
    }
    else
    {
        phost->Control.setup.b.wIndex.w = 0;
    }
    phost->Control.setup.b.wLength.w = length;
    return USBH_CtlReq(pdev, phost, buff , length );
}

/**
 * @brief  USBH_SetAddress
 *         This command sets the address to the connected device
 * @param  pdev: Selected device
 * @param  DeviceAddress: Device address to assign
 * @retval Status
 */
USBH_Status USBH_SetAddress(USB_OTG_CORE_HANDLE *pdev,
                            USBH_HOST *phost,
                            uint8_t DeviceAddress)
{
    phost->Control.setup.b.bmRequestType = USB_H2D | USB_REQ_RECIPIENT_DEVICE | \
        USB_REQ_TYPE_STANDARD;

    phost->Control.setup.b.bRequest = USB_REQ_SET_ADDRESS;

    phost->Control.setup.b.wValue.w = (uint16_t)DeviceAddress;
    phost->Control.setup.b.wIndex.w = 0;
    phost->Control.setup.b.wLength.w = 0;

    return USBH_CtlReq(pdev, phost, 0 , 0 );
}

/**
 * @brief  USBH_SetCfg
 *         The command sets the configuration value to the connected device
 * @param  pdev: Selected device
 * @param  cfg_idx: Configuration value
 * @retval Status
 */
USBH_Status USBH_SetCfg(USB_OTG_CORE_HANDLE *pdev,
                        USBH_HOST *phost,
                        uint16_t cfg_idx)
{

    phost->Control.setup.b.bmRequestType = USB_H2D | USB_REQ_RECIPIENT_DEVICE |\
        USB_REQ_TYPE_STANDARD;
    phost->Control.setup.b.bRequest = USB_REQ_SET_CONFIGURATION;
    phost->Control.setup.b.wValue.w = cfg_idx;
    phost->Control.setup.b.wIndex.w = 0;
    phost->Control.setup.b.wLength.w = 0;

    return USBH_CtlReq(pdev, phost, 0 , 0 );
}

/**
 * @brief  USBH_SetInterface
 *         The command sets the Interface value to the connected device
 * @param  pdev: Selected device
 * @param  itf_idx: Interface value
 * @retval Status
 */
USBH_Status USBH_SetInterface(USB_OTG_CORE_HANDLE *pdev,
                              USBH_HOST *phost,
                              uint8_t ep_num, uint8_t altSetting)
{
    phost->Control.setup.b.bmRequestType = USB_H2D | USB_REQ_RECIPIENT_INTERFACE | \
        USB_REQ_TYPE_STANDARD;

    phost->Control.setup.b.bRequest = USB_REQ_SET_INTERFACE;
    phost->Control.setup.b.wValue.w = altSetting;
    phost->Control.setup.b.wIndex.w = ep_num;
    phost->Control.setup.b.wLength.w = 0;

    return USBH_CtlReq(pdev, phost, 0 , 0 );
}

/**
 * @brief  USBH_SetFeature
 *         The command sets the device features (remote wakeup feature,..)
 * @param  pdev: Selected device
 * @param  itf_idx
 * @retval Status
 */
USBH_Status USBH_SetDeviceFeature(USB_OTG_CORE_HANDLE *pdev,
                                  USBH_HOST *phost,
                                  uint8_t FeatureSelector, uint16_t wIndex)
{
    phost->Control.setup.b.bmRequestType = USB_H2D | USB_REQ_RECIPIENT_DEVICE | \
        USB_REQ_TYPE_STANDARD;

    phost->Control.setup.b.bRequest = USB_REQ_SET_FEATURE;
    phost->Control.setup.b.wValue.w = FeatureSelector;
    phost->Control.setup.b.wIndex.w = wIndex;
    phost->Control.setup.b.wLength.w = 0;

    return USBH_CtlReq(pdev, phost, 0 , 0 );
}


/**
 * @brief  USBH_ClearDeviceFeature
 *         The command sets the device features (remote wakeup feature,..)
 * @param  pdev: Selected device
 * @param  itf_idx
 * @retval Status
 */
USBH_Status USBH_ClearDeviceFeature(USB_OTG_CORE_HANDLE *pdev,
                                    USBH_HOST *phost,
                                    uint8_t FeatureSelector, uint16_t wIndex)
{
    phost->Control.setup.b.bmRequestType = USB_H2D | USB_REQ_RECIPIENT_DEVICE | \
        USB_REQ_TYPE_STANDARD;

    phost->Control.setup.b.bRequest = USB_REQ_CLEAR_FEATURE;
    phost->Control.setup.b.wValue.w = FeatureSelector;
    phost->Control.setup.b.wIndex.w = wIndex;
    phost->Control.setup.b.wLength.w = 0;

    return USBH_CtlReq(pdev, phost, 0 , 0 );
}

/**
 * @brief  USBH_ClrFeature
 *         This request is used to clear or disable a specific feature.

 * @param  pdev: Selected device
 * @param  ep_num: endpoint number
 * @param  hc_num: Host channel number
 * @retval Status
 */
USBH_Status USBH_ClrFeature(USB_OTG_CORE_HANDLE *pdev,
                            USBH_HOST *phost,
                            uint8_t ep_num,
                            uint8_t hc_num)
{

    phost->Control.setup.b.bmRequestType = USB_H2D |
        USB_REQ_RECIPIENT_ENDPOINT |
        USB_REQ_TYPE_STANDARD;

    phost->Control.setup.b.bRequest = USB_REQ_CLEAR_FEATURE;
    phost->Control.setup.b.wValue.w = FEATURE_SELECTOR_ENDPOINT;
    phost->Control.setup.b.wIndex.w = ep_num;
    phost->Control.setup.b.wLength.w = 0;

    if ((ep_num & USB_REQ_DIR_MASK ) == USB_D2H)
    { /* EP Type is IN */
        pdev->host.hc[hc_num].toggle_in = 0;
    }
    else
    {/* EP Type is OUT */
        pdev->host.hc[hc_num].toggle_out = 0;
    }

    return USBH_CtlReq(pdev, phost, 0 , 0 );
}

/**
 * @brief  USBH_ParseDevDesc
 *         This function Parses the device descriptor
 * @param  dev_desc: device_descriptor destination address
 * @param  buf: Buffer where the source descriptor is available
 * @param  length: Length of the descriptor
 * @retval None
 */
static void  USBH_ParseDevDesc (USBH_DevDesc_TypeDef* dev_desc,
                                uint8_t *buf,
                                uint16_t length)
{
    dev_desc->bLength            = *(uint8_t  *) (buf +  0);
    dev_desc->bDescriptorType    = *(uint8_t  *) (buf +  1);
    dev_desc->bcdUSB             = LE16 (buf +  2);
    dev_desc->bDeviceClass       = *(uint8_t  *) (buf +  4);
    dev_desc->bDeviceSubClass    = *(uint8_t  *) (buf +  5);
    dev_desc->bDeviceProtocol    = *(uint8_t  *) (buf +  6);
    dev_desc->bMaxPacketSize     = *(uint8_t  *) (buf +  7);

    if (length > 8)
    { /* For 1st time after device connection, Host may issue only 8 bytes for
         Device Descriptor Length  */
        dev_desc->idVendor           = LE16 (buf +  8);
        dev_desc->idProduct          = LE16 (buf + 10);
        dev_desc->bcdDevice          = LE16 (buf + 12);
        dev_desc->iManufacturer      = *(uint8_t  *) (buf + 14);
        dev_desc->iProduct           = *(uint8_t  *) (buf + 15);
        dev_desc->iSerialNumber      = *(uint8_t  *) (buf + 16);
        dev_desc->bNumConfigurations = *(uint8_t  *) (buf + 17);
    }
}

/**
 * @brief  USBH_ParseCfgDesc
 *         This function Parses the configuration descriptor
 * @param  cfg_desc: Configuration Descriptor address
 * @param  itf_desc: Interface Descriptor address
 * @param  ep_desc: Endpoint Descriptor address
 * @param  buf: Buffer where the source descriptor is available
 * @param  length: Length of the descriptor
 * @retval None
 */
USBH_Status USBH_ParseCfgDesc (USBH_CfgDesc_TypeDef* cfg_desc,
                               USBH_InterfaceDesc_TypeDef* itf_desc,
                               USBH_EpDesc_TypeDef   ep_desc[][USBH_MAX_NUM_ENDPOINTS],
                               uint8_t *buf,
                               uint16_t length)
{
    USBH_InterfaceDesc_TypeDef    *pif ;
    USBH_InterfaceDesc_TypeDef    temp_pif ;
    USBH_EpDesc_TypeDef           *pep;
    USBH_DescHeader_t             *pdesc = (USBH_DescHeader_t *)buf;
    uint16_t                      ptr;
    int8_t                        if_ix = 0;
    int8_t                        ep_ix = 0;
    static uint16_t               prev_ep_size = 0;
    static uint8_t                prev_itf = 0;
    USBH_Status status;


    pdesc   = (USBH_DescHeader_t *)buf;

    /* Parse configuration descriptor */
    cfg_desc->bLength             = *(uint8_t  *) (buf + 0);
    cfg_desc->bDescriptorType     = *(uint8_t  *) (buf + 1);
    cfg_desc->wTotalLength        = LE16 (buf + 2);
    cfg_desc->bNumInterfaces      = *(uint8_t  *) (buf + 4);
    cfg_desc->bConfigurationValue = *(uint8_t  *) (buf + 5);
    cfg_desc->iConfiguration      = *(uint8_t  *) (buf + 6);
    cfg_desc->bmAttributes        = *(uint8_t  *) (buf + 7);
    cfg_desc->bMaxPower           = *(uint8_t  *) (buf + 8);


    if (length > USB_CONFIGURATION_DESC_SIZE)
    {
        ptr = USB_LEN_CFG_DESC;

        if ( cfg_desc->bNumInterfaces <= USBH_MAX_NUM_INTERFACES)
        {
            pif = (USBH_InterfaceDesc_TypeDef *)0;
            while (ptr < cfg_desc->wTotalLength )
            {
                pdesc = USBH_GetNextDesc((uint8_t *)pdesc, &ptr);
                if (pdesc->bDescriptorType   == USB_DESC_TYPE_INTERFACE)
                {
                    if_ix             = *(((uint8_t *)pdesc ) + 2);
                    pif               = &itf_desc[if_ix];
                    if((*((uint8_t *)pdesc + 3)) < 3)
                    {
                        USBH_ParseInterfaceDesc (&temp_pif, (uint8_t *)pdesc);
                        ep_ix = 0;

                        /* Parse Ep descriptors relative to the current interface */
                        if(temp_pif.bNumEndpoints <= USBH_MAX_NUM_ENDPOINTS)
                        {
                            while (ep_ix < temp_pif.bNumEndpoints)
                            {
                                pdesc = USBH_GetNextDesc((void* )pdesc, &ptr);
                                if (pdesc->bDescriptorType   == USB_DESC_TYPE_ENDPOINT)
                                {
                                    pep               = &ep_desc[if_ix][ep_ix];
                                    if(prev_itf != if_ix)
                                    {
                                        prev_itf = if_ix;
                                        USBH_ParseInterfaceDesc (pif, (uint8_t *)&temp_pif);
                                    }
                                    else
                                    {
                                        if(prev_ep_size > LE16((uint8_t *)pdesc + 4))
                                        {
                                            break;
                                        }
                                        else
                                        {
                                            USBH_ParseInterfaceDesc (pif, (uint8_t *)&temp_pif);
                                        }
                                    }
                                    USBH_ParseEPDesc (pep, (uint8_t *)pdesc);
                                    prev_ep_size = LE16((uint8_t *)pdesc + 4);
                                    ep_ix++;
                                }
                            }
                        }
                        else /*num endpoints exceeded */
                        {
                            status = USBH_NOT_SUPPORTED;
                            return status;
                        }
                    }
                }
            }
        }
        else /*num interfaces exceeded */
        {
            status = USBH_NOT_SUPPORTED;
            return status;
        }
        prev_ep_size = 0;
        prev_itf = 0;
    }
    return USBH_OK ;
}


/**
 * @brief  USBH_ParseInterfaceDesc
 *         This function Parses the interface descriptor
 * @param  if_descriptor : Interface descriptor destination
 * @param  buf: Buffer where the descriptor data is available
 * @retval None
 */
static void  USBH_ParseInterfaceDesc (USBH_InterfaceDesc_TypeDef *if_descriptor,
                                      uint8_t *buf)
{
    if_descriptor->bLength            = *(uint8_t  *) (buf + 0);
    if_descriptor->bDescriptorType    = *(uint8_t  *) (buf + 1);
    if_descriptor->bInterfaceNumber   = *(uint8_t  *) (buf + 2);
    if_descriptor->bAlternateSetting  = *(uint8_t  *) (buf + 3);
    if_descriptor->bNumEndpoints      = *(uint8_t  *) (buf + 4);
    if_descriptor->bInterfaceClass    = *(uint8_t  *) (buf + 5);
    if_descriptor->bInterfaceSubClass = *(uint8_t  *) (buf + 6);
    if_descriptor->bInterfaceProtocol = *(uint8_t  *) (buf + 7);
    if_descriptor->iInterface         = *(uint8_t  *) (buf + 8);
}

/**
 * @brief  USBH_ParseEPDesc
 *         This function Parses the endpoint descriptor
 * @param  ep_descriptor: Endpoint descriptor destination address
 * @param  buf: Buffer where the parsed descriptor stored
 * @retval None
 */
static void  USBH_ParseEPDesc (USBH_EpDesc_TypeDef  *ep_descriptor,
                               uint8_t *buf)
{

    ep_descriptor->bLength          = *(uint8_t  *) (buf + 0);
    ep_descriptor->bDescriptorType  = *(uint8_t  *) (buf + 1);
    ep_descriptor->bEndpointAddress = *(uint8_t  *) (buf + 2);
    ep_descriptor->bmAttributes     = *(uint8_t  *) (buf + 3);
    ep_descriptor->wMaxPacketSize   = LE16 (buf + 4);
    ep_descriptor->bInterval        = *(uint8_t  *) (buf + 6);
}

/**
 * @brief  USBH_ParseStringDesc
 *         This function Parses the string descriptor
 * @param  psrc: Source pointer containing the descriptor data
 * @param  pdest: Destination address pointer
 * @param  length: Length of the descriptor
 * @retval None
 */
static void USBH_ParseStringDesc (uint8_t* psrc,
                                  uint8_t* pdest,
                                  uint16_t length)
{
    /* Make sure the Descriptor is String Type */
    if (psrc[1] == USB_DESC_TYPE_STRING) {

        /* The UNICODE string descriptor is not NUL-terminated. The string
         * length is computed from the descriptor length. Divide by two
         * to account for UTF-16 encoding. */
        length = min_t(uint16_t, psrc[0]/2 - 1, length - 1);

        while (length--) {
            psrc += 2;
            *pdest++ = *psrc;
        }

    }

    /* Mark end of string. */
    *pdest = '\0';
}

/**
 * @brief  USBH_GetNextDesc
 *         This function return the next descriptor header
 * @param  buf: Buffer where the cfg descriptor is available
 * @param  ptr: data pointer inside the cfg descriptor
 * @retval next header
 */
USBH_DescHeader_t  *USBH_GetNextDesc (uint8_t   *pbuf, uint16_t  *ptr)
{
    USBH_DescHeader_t  *pnext;

    *ptr += ((USBH_DescHeader_t *)pbuf)->bLength;
    pnext = (USBH_DescHeader_t *)((uint8_t *)pbuf + \
                                  ((USBH_DescHeader_t *)pbuf)->bLength);

    return(pnext);
}

/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/
